#pragma once

#include <cstdint>
#include <utility>
#include <vector>
#include "../deps/mdtx-online/container/container_type_sdeque.h"

namespace mdtx::punch
{

    using OrderSide = std::int8_t;
    constexpr OrderSide OrderSideBuy = 0;
    constexpr OrderSide OrderSideSell = 1;

    using OrderType = std::int8_t;
    constexpr OrderType OrderTypeLimit = 0;
    constexpr OrderType OrderTypeMarket = 1;
    // Should be implemented by broker
    // constexpr OrderType OrderTypeStop = 2;

    using OrderStatus = std::int8_t;
    constexpr OrderStatus OrderStatusNone = 0;
    constexpr OrderStatus OrderStatusPartial = 1;
    constexpr OrderStatus OrderStatusFilled = 2;
    constexpr OrderStatus OrderStatusCancelNone = 3;
    constexpr OrderStatus OrderStatusCancelPartial = 4;
    constexpr OrderStatus OrderStatusMakeCancel = 3;

    struct Order
    {
        // Basic order
        std::uint64_t seq_id;
        std::int64_t price, quant;

        OrderSide side;
        OrderType type;
        OrderStatus status;
        bool gtc, ioc, aon;

        struct BidOrderComparator
        {
            bool operator()(const Order &lhs, const Order &rhs) const
            {
                return (lhs.price > rhs.price) || (lhs.price == rhs.price && lhs.seq_id < rhs.seq_id);
            }
        };
        struct AskOrderComparator
        {
            bool operator()(const Order &lhs, const Order &rhs) const
            {
                return (lhs.price < rhs.price) || (lhs.price == rhs.price && lhs.seq_id < rhs.seq_id);
            }
        };
    };

    constexpr size_t size = sizeof(Order);

    class OrderbookListener
    {
    public:
        virtual void match(std::uint64_t instrument, std::uint64_t seq_id,
                           // after trade
                           const Order &order, const Order &resting_order,
                           // aggressor side, matched price, matched quantity
                           OrderSide side, std::int64_t price, std::int64_t quant) = 0;

        virtual void queue(std::uint64_t instrument, std::uint64_t seq_id,
                           // can be none or partial
                           const Order &order) = 0;

        virtual void cancel(std::uint64_t instrument, std::uint64_t seq_id,
                            const Order &order,
                            std::int64_t quant, std::int64_t remain) = 0;

        // Order listener should not listen for aggregated market events
    };

    class Orderbook
    {
    private:
        std::uint64_t ins, seq;

        mdtx::online::container::sdeque<Order, Order::BidOrderComparator> bid;
        mdtx::online::container::sdeque<Order, Order::AskOrderComparator> ask;
        std::vector<Order> lookup;

        std::shared_ptr<OrderbookListener> callback;

        std::vector<Order> aon_pop; // reused

    public:
        Orderbook(std::uint64_t instrument, std::shared_ptr<OrderbookListener> listener)
            : ins(instrument),
              seq(0),
              bid(),
              ask(),
              callback(listener)
        {
            lookup.push_back({});
        }

        Order submit(OrderSide side, OrderType type, std::int64_t price, std::int64_t quant,
                     bool gtc = false, bool ioc = false, bool aon = false)
        {
            ++seq;
            lookup.push_back({seq, price, quant, side, type, OrderStatusNone, gtc, ioc, aon});
            Order &new_order = lookup[seq];
            //Order &new_order = (lookup[seq] = {seq, price, quant, side, type, OrderStatusNone, gtc, ioc, aon});

            if (side == OrderSideBuy)
            {
                if (aon)
                {
                    exec_buy_aon(new_order);
                }
                else
                {
                    exec_buy(new_order);
                }
            }
            else
            {
                if (aon)
                {
                    exec_sell_aon(new_order);
                }
                else
                {
                    exec_sell(new_order);
                }
            }

            return new_order;
        }

        void cancel(std::uint64_t id, std::int64_t quant)
        {
            if (id > seq)
            {
                return;
            }

            Order &order = lookup[id];
            std::int64_t cancelled = 0;

            if (order.status > 1)
            {
                // order is filled / already cancelled
                return;
            }
            if (quant == 0 || quant >= order.quant)
            {
                // cancel all
                cancelled = order.quant;
                order.quant = 0;
                order.status += OrderStatusMakeCancel;
                if (order.side == OrderSideBuy)
                {
                    bid.erase(order);
                }
                else
                {
                    ask.erase(order);
                }
                callback->cancel(ins, id, order, cancelled, 0);
            }
            else
            {
                // update quant
                cancelled = quant;
                order.quant -= quant;
                if (order.side == OrderSideBuy)
                {
                    bid.find(order)->quant = order.quant;
                }
                else
                {
                    ask.find(order)->quant = order.quant;
                }
                callback->cancel(ins, id, order, cancelled, order.quant);
            }
        }

    private:
        void exec_buy(Order &order)
        {
            aon_pop.clear();

            while (order.quant && !ask.empty())
            {
                const Order &best = ask.top();
                if (best.price > order.price && order.type == OrderTypeLimit)
                {
                    // do not break if market order
                    break;
                }
                if (best.aon && best.quant > order.quant)
                {
                    // BBO is AON and can't be filled all, ignore and jump to next BBO
                    aon_pop.push_back(ask.pop());
                }
                else
                {
                    exec_bbo(order, ask);
                }
            }
            // Push back popped AON resting orders
            for (auto it = aon_pop.rbegin(); it != aon_pop.rend(); ++it)
            {
                ask.insert(*it);
            }
            // Order not filled, or filled partially, add to queue
            if (order.quant)
            {
                // cancel IOC and market order
                if (order.ioc || order.type == OrderTypeMarket)
                {
                    exec_cancel_current(order);
                }
                else
                {
                    bid.insert(order);
                    callback->queue(ins, order.seq_id, order);
                }
            }
        }

        void exec_buy_aon(Order &order)
        {
            aon_pop.clear();
            std::int64_t remain_quant = order.quant;

            // Query resting orders
            for (auto it = ask.cbegin(); it != ask.cend(); ++it)
            {
                if (remain_quant <= 0 || (it->price > order.price && order.type == OrderTypeLimit))
                {
                    // order can be filled all, or resting price can't match
                    break;
                }
                if (!it->aon || it->quant <= remain_quant)
                {
                    // resting order is not AON, or it can be filled all
                    remain_quant -= it->quant;
                }
            }
            if (remain_quant <= 0)
            {
                // order can be filled all
                while (order.quant)
                {
                    const Order &best = ask.top();
                    if (best.aon && best.quant > order.quant)
                    {
                        // BBO is AON and can't be filled all, ignore and jump to next BBO
                        aon_pop.push_back(ask.pop());
                    }
                    else
                    {
                        // Execute
                        exec_bbo(order, ask);
                    }
                }
                // Push back popped AON resting orders
                for (auto it = aon_pop.rbegin(); it != aon_pop.rend(); ++it)
                {
                    ask.insert(*it);
                }
            }
            else
            {
                // order can't be filled all
                if (order.ioc || order.type == OrderTypeMarket)
                {
                    // cancel FOK order
                    exec_cancel_current(order);
                }
                else
                {
                    bid.insert(order);
                    callback->queue(ins, order.seq_id, order);
                }
            }
        }

        void exec_sell(Order &order)
        {
            aon_pop.clear();

            while (order.quant && !bid.empty())
            {
                const Order &best = bid.top();
                if (best.price < order.price && order.type == OrderTypeLimit)
                {
                    // do not break if market order
                    break;
                }
                if (best.aon && best.quant > order.quant)
                {
                    // BBO is AON and can't be filled all, ignore and jump to next BBO
                    aon_pop.push_back(bid.pop());
                }
                else
                {
                    exec_bbo(order, bid);
                }
            }
            // Push back popped AON resting orders
            for (auto it = aon_pop.rbegin(); it != aon_pop.rend(); ++it)
            {
                bid.insert(*it);
            }
            // Order not filled, or filled partially, add to queue
            if (order.quant)
            {
                // cancel IOC and market order
                if (order.ioc || order.type == OrderTypeMarket)
                {
                    exec_cancel_current(order);
                }
                else
                {
                    ask.insert(order);
                    callback->queue(ins, order.seq_id, order);
                }
            }
        }

        void exec_sell_aon(Order &order)
        {
            aon_pop.clear();
            std::int64_t remain_quant = order.quant;

            // Query resting orders
            for (auto it = bid.cbegin(); it != bid.cend(); ++it)
            {
                if (remain_quant <= 0 || (it->price < order.price && order.type == OrderTypeLimit))
                {
                    // order can be filled all, or resting price can't match
                    break;
                }
                if (!it->aon || it->quant <= remain_quant)
                {
                    // resting order is not AON, or it can be filled all
                    remain_quant -= it->quant;
                }
            }
            if (remain_quant <= 0)
            {
                // order can be filled all
                while (order.quant)
                {
                    const Order &best = bid.top();
                    if (best.aon && best.quant > order.quant)
                    {
                        // BBO is AON and can't be filled all, ignore and jump to next BBO
                        aon_pop.push_back(bid.pop());
                    }
                    else
                    {
                        // Execute
                        exec_bbo(order, bid);
                    }
                }
                // Push back popped AON resting orders
                for (auto it = aon_pop.rbegin(); it != aon_pop.rend(); ++it)
                {
                    bid.insert(*it);
                }
            }
            else
            {
                // order can't be filled all
                if (order.ioc || order.type == OrderTypeMarket)
                {
                    // cancel FOK order
                    exec_cancel_current(order);
                }
                else
                {
                    ask.insert(order);
                    callback->queue(ins, order.seq_id, order);
                }
            }
        }

        template <class QueueType>
        void exec_bbo(Order &order, QueueType &queue)
        {
            std::int64_t exec_quant;

            if (order.type == OrderTypeMarket)
            {
                order.price = queue.top().price;
            }
            // what if queue.top() is AON?
            if (queue.top().quant > order.quant)
            {
                Order &resting = queue.top();
                exec_quant = order.quant;

                order.quant = 0;
                order.status = OrderStatusFilled;
                lookup[order.seq_id] = order;

                resting.quant -= exec_quant;
                resting.status = OrderStatusPartial;
                lookup[resting.seq_id] = resting;

                callback->match(ins, order.seq_id, order, resting, order.side, resting.price, exec_quant);
            }
            else
            {
                Order resting = queue.pop();
                exec_quant = resting.quant;

                order.quant -= exec_quant;
                order.status = OrderStatusPartial;
                lookup[order.seq_id] = order;

                resting.quant = 0;
                resting.status = OrderStatusFilled;
                lookup[resting.seq_id] = resting;

                callback->match(ins, order.seq_id, order, resting, order.side, resting.price, exec_quant);
            }
        }

        void exec_cancel_current(Order &order)
        {
            std::int64_t tmp = order.quant;
            order.quant = 0;
            order.status += OrderStatusMakeCancel;
            lookup[order.seq_id] = order;

            callback->cancel(ins, order.seq_id, order, tmp, 0);
        }

        std::vector<std::int64_t> __dbg_get_ask_price()
        {
            std::vector<std::int64_t> ask_price;
            for (auto it = ask.cbegin(); it != ask.cend(); ++it)
            {
                ask_price.push_back(it->price);
            }
            return ask_price;
        }

        std::vector<std::int64_t> __dbg_get_bid_price()
        {
            std::vector<std::int64_t> bid_price;
            for (auto it = bid.cbegin(); it != bid.cend(); ++it)
            {
                bid_price.push_back(it->price);
            }
            return bid_price;
        }
    };

}
